package com.taglab.chic.taglib;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.http.HttpServletRequest;

import java.io.File;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.text.DateFormat;

/**
 * <p>
 *   Appends a versioning attribute to a URL, based on the last modified date of the underlying resource.
 * </p>
 *
 * <p>
 *   For example, <code>&lt;chic:vurl value="/myurl/myresource"&gt;</code> will be output as
 * <code>/myurl/myresource?v=0903271003</code>.
 * </p>
 *
 * <p>
 *   The tag is primarily used to version resources which are aggressively cached by browsers, such as stylesheets. The
 * tag also supports scopes and variables in a similar way to <code>&lt;c:url&gt;</code>.
 * </p>
 *
 * @author Damien Papworth
 */
public class VersionedUrlTag extends TagSupport {
  private static final CacheManager cacheManager = CacheManager.getInstance();
  static {
    // Create an in-memory cache with elements expiring after 5 minutes
    Cache memoryOnlyCache = new Cache("com.taglab.chic.taglib.VersionedUrlTag", 100, false, false, 300, 0);
    cacheManager.addCache(memoryOnlyCache);
  }

  private static final String REQUEST = "request";
  private static final String SESSION = "session";
  private static final String APPLICATION = "application";

  private final DateFormat versioningFormat = new SimpleDateFormat("yyMMddHHmm");

  // Tag attributes
  private String value;
  private String var;
  private int scope;

  public VersionedUrlTag() {
    super();
    init();
  }

  private void init() {
    value = null;
    var = null;
    scope = PageContext.PAGE_SCOPE;
  }

  public void setValue(String value) {
    this.value = value;
  }

  public void setVar(String var) {
    this.var = var;
  }

  public void setScope(String scopeString) {
    scope = PageContext.PAGE_SCOPE;

    // Translate scope to PageContext equivalent
    if (REQUEST.equalsIgnoreCase(scopeString))
	    scope = PageContext.REQUEST_SCOPE;
	  else if (SESSION.equalsIgnoreCase(scopeString))
	    scope = PageContext.SESSION_SCOPE;
	  else if (APPLICATION.equalsIgnoreCase(scopeString))
	    scope = PageContext.APPLICATION_SCOPE;
  }

  public void release() {
    // Release resources
	  init();
  }

  public int doEndTag() throws JspException {
    String webappPath = determineWebappPath(value);

    Cache cache = cacheManager.getCache("com.taglab.chic.taglib.VersionedUrlTag");
    Element element = cache.get(webappPath);
    if(element == null) {
      String version = generateVersionString(webappPath);
      element = new Element(webappPath, version);
      cache.put(element);
    }
    String versionedUrl = appendVersioning(value, (String) element.getObjectValue());

    if (var != null) {
      // Add variable to context
      pageContext.setAttribute(var, versionedUrl, scope);
    } else {
      try {
        // Output value
        pageContext.getOut().print(versionedUrl);
      } catch (java.io.IOException ex) {
        throw new JspTagException(ex.toString(), ex);
      }
    }

    return EVAL_PAGE;
  }

  protected String appendVersioning(String url, String version) {
    // XXX Change versioning parameter to avoid clashes with other parameters?

    // Build versioning parameter for query string
    StringBuffer versioningParam = new StringBuffer("v=");
    versioningParam.append(version);

    StringBuffer workingUrl = new StringBuffer(url);
    int questionMark = workingUrl.indexOf("?");
    if (questionMark == -1) {
      // Append new query string containing versioning
      workingUrl.append("?");
      workingUrl.append(versioningParam);
    } else {
      // Insert parameter into existing query string
      versioningParam.append("&");
      workingUrl.insert(questionMark + 1, versioningParam);
    }
    return workingUrl.toString();
  }

  protected String determineWebappPath(String url) {
    // XXX Add check for external path
    String realPath;
    if(url.startsWith("/")) {
      // Absolute path
      realPath = pageContext.getServletContext().getRealPath(url);
    } else {
      // Relative to current path
      HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
      StringBuffer path = new StringBuffer(request.getRequestURI());

      // Remove context path
      int contextPathLength = request.getContextPath().length();
      if (contextPathLength > 0) {
        path.delete(0, contextPathLength);
      }

      // Get parent directory
      int pathEnd = path.lastIndexOf("/");
      if(pathEnd != -1 && pathEnd + 1 != path.length()) {
        path.delete(pathEnd + 1, path.length());
      }

      realPath = pageContext.getServletContext().getRealPath(path.append(url).toString());
    }
    return realPath;
  }

  protected String generateVersionString(String webappPath) {
   return versioningFormat.format(new Date(new File(webappPath).lastModified()));
  }
}
